/**
 * @brief 一个简单的数据记录保存文件引擎。可以配置保存多少条数据内容，方便按照顺序保存内容，如果超出保存的数量则移除旧内容.
 * @version 1.0
 * @author 宋炜
 */

#pragma once
#include <string>
#include <unordered_map>
#include <fstream>
#include <mutex>
#include <atomic>

#include "mallocSharedPtr.hpp"
namespace wheels{
#if defined( __GNUC__ )
#   define  ALIGN_TO_ONE       __attribute__((packed))
#elif defined( _MSC_VER )
#   define  ALIGN_TO_ONE       
#endif

#if __cplusplus >= 201703L
#   include <shared_mutex>
    using mutexType_t = std::shared_mutex;
#   define mutexSharedLock(  LCK , mutex )  std::shared_lock< mutexType_t > LCK( mutex )
#   define mutexUniqueLock( LCK , mutex ) std::unique_lock< mutexType_t > LCK( mutex )
#elif __cplusplus >= 201103L
    using mutexType_t = std::mutex;
#   define mutexSharedLock(  LCK , mutex )  std::unique_lock< mutexType_t > LCK( mutex )
#   define mutexUniqueLock( LCK , mutex ) std::unique_lock< mutexType_t > LCK( mutex )
#else
#   error( "不支持的C++版本，请选择C++11及以上标准进行编译" )
#endif

class tinySeqFile
{
public:
    enum class emCrypto{ NONE = 0 , AES128 = 1, AES256 = 2};
#if defined( _MSC_VER )
#   pragma pack( push , 1 )
#endif
    struct stHead{
        char m_magic[ 4 ] = { 'T' ,'F','\0','\0'};
        uint16_t   m_ver = 1;            // 文件格式版本号
        uint64_t   m_count;              // 数据条数
        uint64_t   m_max_count;          // 最大存储数据条数
        uint64_t   m_length;             // 文件总字节数量
        uint64_t   m_start_idx;          // 队列头，以环形逻辑记录
        uint32_t   m_page_size;          // 分页大小，每一条数据的最大保存长度。所有数据偏移以分页大小计算。虽然浪费点磁盘但是避免了
                                        // 动态调整数据的动作，提高了运行效率。但是要保证程序运行的时候需要顾及好最大分页，以提高磁盘的利用率
        uint8_t    m_crypto;             // 加密方法, 当前版本仅仅支持NONE，即明文保存
        uint64_t   m_curr_idx;           // 下一个数据要保存的索引，当索引数量超过了最大条数则覆盖头部的内容
    }ALIGN_TO_ONE;

    struct stIndexItem{
        uint64_t   m_idx;             // 数据索引，使用std::map在程序启动的时候在如所有的头信息，此后根据数据偏移量计算保存的数据。
                                      // 如果数据长度超过了分页长度，则截断保存数据
        uint32_t  m_len;              // 数据实际长度
        time_t    m_timestamp;        // 更新数据的时间戳，以unix时间戳为准
    }ALIGN_TO_ONE;

#if defined( _MSC_VER )
#pragma pack( pop )
#endif
private:
    /**
     * @brief 加密写入数据
     * @param data 要写入的数据
     * @return true 写入成功
     * @return false 写入失败
     */
    bool aes128_write__( const std::string& data );
    bool aes128_write__( const wheels::mallocSharedPtr< uint8_t > data );
    bool aes256_write__( const std::string& data );
    bool aes256_write__( const wheels::mallocSharedPtr< uint8_t > data );
    /**
     * @brief 解密读取数据
     * @param data 
     * @return true 
     * @return false 
     */
    bool aes128_read__( std::string& data );
    bool aes128_read__( wheels::mallocSharedPtr< uint8_t > data );
    bool aes256_read__( std::string& data );
    bool aes256_read__( wheels::mallocSharedPtr< uint8_t > data );
    /**
     * @brief 同步文件头信息
     * @return true 
     * @return false 
     */
    bool sync__();
private:
    stHead                                m_head__;         // 文件头
    std::unordered_map< int64_t , stIndexItem >     m_idxs__;     // 索引

    std::fstream                          m_fs__;           // 文件流对象
    mutexType_t                           m_mutex__;        // 互斥锁
    std::atomic<bool >                    m_is_synced__;    // 是否已经同步
    std::atomic< bool >                   m_is_inited__;
    
    std::string                           m_aes_iv;         // aes128 初始化向量
    std::string                           m_aes_key;        // aes128 密钥
public:
    /** 
     * @brief 构造函数
     * @param path 文件路径
     * @param count 最大保存数据条数
     * @param page 分页大小
     */
    tinySeqFile( const std::string& path , uint64_t count , uint32_t page = 10 * 1024 );
    ~tinySeqFile();
    /**
     * @brief 设置加密方法
     * @param type 加密方法
     * @param key 密钥
     * @param iv 初始化向量
     * @return true 
     * @return false 
     */
    bool setCrypto( emCrypto type , std::string& key , std::string& iv );
    /**
     * @brief 入队
     * @param data 要入队的数据
     * @return true 入队成功
     * @return false 入队失败
     */
    bool push( const std::string& data );
    bool push( const wheels::mallocSharedPtr< uint8_t > data );
    /**
     * @brief 读取队列头部数据
     * @param data 出队的数据
     * @return true 出队成功
     * @return false 出队失败
     */
    bool front( std::string& data );
    bool front( wheels::mallocSharedPtr< uint8_t > data );
    /**
     * @brief 返回第一个索引
     */
    uint64_t firstIndex(){ return m_head__.m_start_idx; }
    /**
     * @brief 读取队列指定索引数据
     * @param idx 索引
     * @param data 出队的数据
     * @return true 出队成功
     * @return false 出队失败
     */
    bool at( int64_t idx , std::string& data );
    bool at( int64_t idx , wheels::mallocSharedPtr< uint8_t > data );
    /**
     * @brief 出队
     */
    void pop_front();
    /**
     * @brief 出队
     */
    void pop_tail();
    /**
     * @brief 删除指定索引数据
     * @param idx 索引
     * @return true 删除成功
     * @return false 删除失败
     */
    bool erase( uint64_t idx );
    /**
     * @brief 同步文件头信息
     * @return true 
     * @return false 
     */
    bool sync(){ return sync__(); }
    /**
     * @brief 获取队列数据条数
     * @return int64_t 数据条数
     */
    uint64_t count();
    /**
     * @brief 清理所有内容，实际仅仅标记文件索引头信息，不实际删除数据，仅仅标记索引
     * @return true 
     * @return false 
     */
    bool clear();
};
}